package vcp.server;

import java.io.IOException;
import java.util.EnumMap;
import java.util.Timer;
import java.util.Vector;

import ocsf.server.AbstractServer;
import ocsf.server.ConnectionToClient;

import org.joda.time.DateTime;

import vcp.client.request.Request;
import vcp.client.request.response.ResponseRequest;
import vcp.client.request.response.ResponseTimeChange;
import vcp.entity.User;
import vcp.enums.RequestType;
import vcp.server.deamontasks.EndDayReservationCleanUpTask;
import vcp.server.deamontasks.EndDayStatisticsTask;
import vcp.server.deamontasks.EndOfSubscriptionTask;
import vcp.server.deamontasks.EndWeekStatisticsTask;
import vcp.server.deamontasks.ReservedLotReminderTask;
import vcp.server.deamontasks.User24HourComplaintNotificationTask;
import vcp.server.deamontasks.UserDBCleanUpTask;
import vcp.server.factories.DAOFactoryAbstract;
import vcp.server.factories.MySQLDAOFactory;
import vcp.server.interfaces.IDateTimeDelegate;
import vcp.server.interfaces.IServer;
import vcp.server.requesthandler.CancelSubscriptionFormRequestHandler;
import vcp.server.requesthandler.CustomerCheckInRequestHandler;
import vcp.server.requesthandler.CustomerCheckoutRequestHandler;
import vcp.server.requesthandler.CustomerRegularCheckInRequestHandler;
import vcp.server.requesthandler.FillComplaintRequestHandler;
import vcp.server.requesthandler.InformationRetrieveRequestHandler;
import vcp.server.requesthandler.PeriodicReportRetrieveRequestHandler;
import vcp.server.requesthandler.PeriodicReportSaveRequestHandler;
import vcp.server.requesthandler.RequestHandler;
import vcp.server.requesthandler.RetrieveStatisticsRequestHandler;
import vcp.server.requesthandler.SiteCostApproveRequestHandler;
import vcp.server.requesthandler.SiteCostUpdateRequestHandler;
import vcp.server.requesthandler.SiteMapSaveRequestHandler;
import vcp.server.requesthandler.SubmitSubscriptionFormRequestHandler;
import vcp.server.requesthandler.UpdateComplaintRequestHandler;
import vcp.server.requesthandler.UserLoginRequestHandler;
import vcp.server.requesthandler.UserLogoutRequestHandler;
import vcp.server.utility.Constants;
import vcp.server.utility.SystemClock;
import vcp.server.utility.timedecision.TimeDecision;

@SuppressWarnings("rawtypes")
/**
 * 
 * @author VCP team
 *server class
 */
public class VCPServer extends AbstractServer implements IDateTimeDelegate, IServer
{
	protected final EnumMap<RequestType, RequestHandler> requestHandlers;
	private final DBTablesIDGenerator idGenerator;
	private final Object dbLock;

	@Override
	public synchronized Object getDBLock()
	{
		return this.dbLock;
	}

	private final DAOFactoryAbstract daoFactory;

	@Override
	public DAOFactoryAbstract getDaoFactory()
	{
		return this.daoFactory;
	}

	/**
	 * class constructor
	 * 
	 * @param port
	 * @param timeDecision
	 */
	public VCPServer(int port, TimeDecision timeDecision)
	{
		super(port);
		this.requestHandlers = new EnumMap<RequestType, RequestHandler>(RequestType.class);
		this.dbLock = new Object();
		this.idGenerator = new DBTablesIDGenerator();
		this.daoFactory = new MySQLDAOFactory();

		this.initHandlersMap();
		this.initSystemClock(timeDecision);
		this.initDeamonList(new Timer(), timeDecision);
	}

	/****************************************************************************************************************************
	 *** Public methods ***
	 ****************************************************************************************************************************/
	/*
	 * (non-Javadoc)
	 * 
	 * @see vcp.server.IServer#getConnections()
	 */
	@Override
	public synchronized ConnectionToClient[] getConnections()
	{
		Vector<ConnectionToClient> connections = new Vector<>();
		for (Thread threadConnection : this.getClientConnections())
		{
			connections.add((ConnectionToClient) threadConnection);
		}

		return connections.toArray(new ConnectionToClient[connections.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see vcp.server.IServer#getGeneratedID(java.lang.String)
	 */
	@Override
	public synchronized Integer getGeneratedID(String tableName)
	{
		return this.idGenerator.generateID(tableName);
	}

	/****************************************************************************************************************************
	 *** Private methods ***
	 ****************************************************************************************************************************/
	/**
	 * initialize system clock
	 * 
	 * @param timeDecision
	 */
	private void initSystemClock(TimeDecision timeDecision)
	{
		SystemClock.getClockInstance().initSystemClock(this, timeDecision);
	}

	/**
	 * initialize handler map
	 */
	protected void initHandlersMap()
	{
		this.requestHandlers.put(RequestType.CUSTOMER_CHECK_IN, new CustomerCheckInRequestHandler(this));
		this.requestHandlers.put(RequestType.USER_LOGIN, new UserLoginRequestHandler(this));
		this.requestHandlers.put(RequestType.CUSTOMER_REGULAR_CHECK_IN, new CustomerRegularCheckInRequestHandler(this));
		this.requestHandlers.put(RequestType.CUSTOMER_CHECK_OUT, new CustomerCheckoutRequestHandler(this));
		this.requestHandlers.put(RequestType.SITE_MAP_SAVE, new SiteMapSaveRequestHandler(this));
		this.requestHandlers.put(RequestType.SUBMIT_SUBSCRIPTION_FORM, new SubmitSubscriptionFormRequestHandler(this));
		this.requestHandlers.put(RequestType.USER_LOGOUT, new UserLogoutRequestHandler(this));
		this.requestHandlers.put(RequestType.SUBMIT_COMPLAINT_FORM, new FillComplaintRequestHandler(this));
		this.requestHandlers.put(RequestType.UPDATE_COMPLAINT, new UpdateComplaintRequestHandler(this));
		this.requestHandlers.put(RequestType.SUBMIT_CANCEL_FORM, new CancelSubscriptionFormRequestHandler(this));
		this.requestHandlers.put(RequestType.SITE_COST_UPDATE, new SiteCostUpdateRequestHandler(this));
		this.requestHandlers.put(RequestType.SITE_COST_APPROVAL, new SiteCostApproveRequestHandler(this));
		this.requestHandlers.put(RequestType.PERIODIC_REPORT_SAVE, new PeriodicReportSaveRequestHandler(this));
		this.requestHandlers.put(RequestType.PERIODIC_REPORT_RETRIEVE, new PeriodicReportRetrieveRequestHandler(this));
		this.requestHandlers.put(RequestType.RETRIEVE_INFORAMTION, new InformationRetrieveRequestHandler(this));
		this.requestHandlers.put(RequestType.RETRIEVE_STATISTICS, new RetrieveStatisticsRequestHandler(this));
	}

	/**
	 * initialize daemon list
	 * 
	 * @param timeDecision
	 * @param delay
	 * 
	 * @param tasksScheduler
	 */
	protected void initDeamonList(final Timer timerManager, final TimeDecision timeDecision)
	{
		timerManager.scheduleAtFixedRate(new UserDBCleanUpTask(this), 0, 2 * 1000 * 60);
		timerManager.scheduleAtFixedRate(new User24HourComplaintNotificationTask(this), timeDecision.getUser24HourComplaintNotificationDelay(), timeDecision.getUser24HourComplaintNotification());
		timerManager.scheduleAtFixedRate(new EndDayReservationCleanUpTask(this), timeDecision.getEndDayReservationCleanUpTaskDelay(), timeDecision.getEndDayReservationCleanUpTask());
		timerManager.scheduleAtFixedRate(new EndDayStatisticsTask(this), timeDecision.getEndDayStatisticsTaskDelay(), timeDecision.getEndDayStatisticsTask());
		timerManager.scheduleAtFixedRate(new EndWeekStatisticsTask(this), timeDecision.getEndWeekStatisticsTaskDelay(), timeDecision.getEndWeekStatisticsTask());
		timerManager.scheduleAtFixedRate(new ReservedLotReminderTask(this), timeDecision.getReservedLotReminderTaskDelay(), timeDecision.getReservedLotReminderTask());
		timerManager.scheduleAtFixedRate(new EndOfSubscriptionTask(this), timeDecision.getEndOfSubscriptionTaskDelay(), timeDecision.getEndOfSubscriptionTask());
	}

	/**
	 * sends time to client
	 * 
	 * @param timeChange
	 * @param client
	 */
	private synchronized void sendTimeToClient(ResponseTimeChange timeChange, ConnectionToClient client)
	{
		try
		{
			client.sendToClient(timeChange);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	/****************************************************************************************************************************
	 *** AbstractServer methods ***
	 ****************************************************************************************************************************/
	@SuppressWarnings("unchecked")
	@Override
	protected void handleMessageFromClient(Object msg, ConnectionToClient client)
	{
		synchronized (this.getDBLock())
		{
			try
			{
				Thread.sleep(1000);
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}

			System.out.println("Message received: " + msg + " from " + client);
			if (msg instanceof Request)
			{
				Request request = (Request) msg;

				RequestType commandType = request.getRequest();
				RequestHandler<Request> handler = this.requestHandlers.get(commandType);
				if (handler != null)
				{
					try
					{
						handler.execute(request, client);
					} catch (Exception e)
					{
						ResponseRequest responseRequest = new ResponseRequest(request.getRequestID(), null);
						responseRequest.setErrorMessage("Request: " + commandType.toString() + " has crushed on the way to:" + client);
						try
						{
							client.sendToClient(responseRequest);
						} catch (IOException ioe)
						{
							ioe.printStackTrace();
						}
						e.printStackTrace();
					}
				}
			}
		}
	}

	@Override
	protected synchronized void clientConnected(ConnectionToClient client)
	{
		System.out.println("new client connected: " + client.getInetAddress());
		client.setInfo("IP Address", client.getInetAddress());

		this.sendTimeToClient(new ResponseTimeChange(SystemClock.getClockInstance().getSystemDateTime()), client);
	}

	@Override
	protected synchronized void clientDisconnected(ConnectionToClient client)
	{
		System.out.println("client disconnected: " + client.getInfo(Constants.IP_ADDRESS));
		client.setInfo(User.class.toString(), null);
	}

	@Override
	protected void serverStarted()
	{
		System.out.println("Server is up on port:" + this.getPort());
	}

	@Override
	protected void serverStopped()
	{
		System.out.println("Server is down & has stopped listening for connections");
	}

	/****************************************************************************************************************************
	 *** IDateTimeDelegate methods ***
	 ****************************************************************************************************************************/
	@Override
	public void onDateTimeChange(DateTime dateTime)
	{
		ResponseTimeChange timeChange = new ResponseTimeChange(dateTime);
		for (ConnectionToClient client : this.getConnections())
		{
			this.sendTimeToClient(timeChange, client);
		}
	}
}
